package com.rattsoftware.server.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.Authentication;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.vote.AffirmativeBased;

import com.rattsoftware.server.admin.model.User;
import com.rattsoftware.server.admin.model.exceptions.RattsDisabledUserException;
import com.rattsoftware.server.admin.model.exceptions.RattsSecurityException;
import com.rattsoftware.server.admin.model.exceptions.RattsSessionExpiredException;
import com.rattsoftware.server.security.util.RattsSecurityUtil;
import com.rattsoftware.server.service.UserService;
import com.rattsoftware.server.serviceImpl.UserServiceImpl;

/**
 * Spring Security Decision Manager used to determine if a user's session has expired and
 * then throw the OpenXDataSessionExpiredException instead of the OpenXDataSecurityException.
 * The SessionExpiredSession will prompt a re-login dialog box on the client so the user 
 * can continue working.
 */
public class RattsAccessDecisionManager extends AffirmativeBased {
	
	@Autowired
	private UserServiceImpl userService;
    @Autowired
    private RattsSessionRegistry sessionRegistry;
	
    /** The logger. */
    private Logger log = LoggerFactory.getLogger(RattsAccessDecisionManager.class);

	@Override
	public void decide(Authentication auth, Object obj,
			ConfigAttributeDefinition config) throws AccessDeniedException {
		try {
            mayBeFireUserDisabledException();
			super.decide(auth, obj, config);
		} catch (AccessDeniedException exception) {
	        if (isUserLoggedIn()) {
	        	//We handle a security exception after
	        	//confirming the User is still logged in
	        	//But lacks permission to accomplish operation.
	        	logAndFireSecurityException(exception);
	        } else {
	        	//We handle a session expiry after affirming
	        	//that the User is no longer in the Security Context.
	        	logAndFireSessionExpiredException(exception);
	        }
		}
	}

	/**
	 * Handles the {@link OpenXDataSecurityException}.
	 * 
	 * @param exception <tt>Exception</tt> thrown.
	 * @throws OpenXDataSecurityException With meaningful <tt>Message</tt> to the <tt>User.</tt>
	 */
	private void logAndFireSecurityException(Exception exception) throws RattsSecurityException {
		//TODO This message should be internationalized. 
		String exMsg = "Access to restricted operation is denied";
		
		// log the error on the server so it is not lost
		log.debug("Caught server side Access Denied exception, throwing new exception to the client '"+ 
				exception.getMessage() +"'", exception);
		
		//Re throw known exception to the User.
		throw new RattsSecurityException(exMsg);
	}

	/**
	 * Handles the {@link OpenXDataSessionExpiredException}.
	 * 
	 * @param exception <tt>Exception</tt> thrown.
	 * @throws OpenXDataSessionExpiredException With meaningful <tt>Message</tt> to the <tt>User.</tt>
	 */
	private void logAndFireSessionExpiredException(Exception exception) throws RattsSessionExpiredException {
		//TODO This message should be internationalized.	        	
		String exMsg = "Your session has expired. Re-Login to proceed.";
		
		// log the error on the server so it is not lost
		log.debug("Caught server side Session Expired exception, throwing new exception to the client '"+ 
				exception.getMessage()+"'", exception);
		
		// Logout User.
		userService.logout();
		
		//Re throw known exception to the User.
		throw new RattsSessionExpiredException(exMsg);
	}
	
	/**
	 * Checks if the <code>User</code> is still 
	 * registered in the <code>Spring security context.</code>
	 * 
	 * @return true if the authentication object can be found and is OpenXDataUserDetails
	 */
	private boolean isUserLoggedIn() {
		// see if the authentication object is intact.
        SecurityContext context = SecurityContextHolder.getContext();
        if (context != null) {
        	
        	// Get the User's authentication if it is still intact.
            Authentication auth = context.getAuthentication();            
            if (auth != null) {
            	
            	// Check if the security context is still intact.
                if (auth.getPrincipal() instanceof RattsUserDetails 
                		|| auth.getPrincipal() instanceof User) {
                    return true;
                }
            }
        }
     
        // Return false if Security Context has been invalidated.
        return false;
	}

    private void mayBeFireUserDisabledException() {
        User user = null;
        try {
            user = RattsSecurityUtil.getLoggedInUser();
        } catch (RattsSessionExpiredException e) {
        }

        if(sessionRegistry.containsDisabledUser(user))
            throw new RattsDisabledUserException("User : "+user.getUsername()+ " is Disabled");
    }

}